package com.example.springstack.springboot.spring5.aop;

import com.alibaba.fastjson.JSON;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Qiangfanghao
 * aop的代理对象
 * @Order(1) 代理对象的优先级,数值越小优先级越高
 *
 * 切入点表达式
 * 1) *：匹配所有字符
 * 2) ..：一般用于匹配多个包，多个参数
 * 3) +：表示类及其子类
 * 4) 运算符有：&&,||,!
 */
@Order(1)
@Component
@Aspect
public class SpringAopDemoProxy {




    /**
     * 定义切入点
     * 切入点为om.example.springstack.springboot.spring5.aop下的所有函数
     */
    @Pointcut("execution(* com.example.springstack.springboot.spring5.aop.SpringAopDemo.*(..))")
    public void pointcut(){}

    /**
     * 在切入点之前执行
     * @Before:
     *  在某连接点之前执行的通知，除非抛出一个异常，
     *  否则这个通知不能阻止连接点之前的执行流程。
     */
    @Before(value = "pointcut()")
    public void before(JoinPoint joinPoint){
        System.out.println("在切入点之前执行了.....");
        //获取目标方法的参数信息
        Object[] obj = joinPoint.getArgs();
        //AOP代理类的信息
        joinPoint.getThis();
        //代理的目标对象
        joinPoint.getTarget();
        //用的最多 通知的签名
        Signature signature = joinPoint.getSignature();
        //代理的是哪一个方法
        System.out.println("代理的是哪一个方法------------>"+signature.getName());
        //AOP代理类的名字
        System.out.println("AOP代理类的名字------------>"+signature.getDeclaringTypeName());
        //AOP代理类的类（class）信息
        Class declaringType = signature.getDeclaringType();
        System.out.println("declaringType------->" +declaringType.toGenericString());
        //获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        //从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        //如果要获取Session信息的话，可以这样写：
        //HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
        //获取请求参数
        Enumeration<String> enumeration = request.getParameterNames();
        Map<String,String> parameterMap = new HashMap<>();
        while (enumeration.hasMoreElements()){
            String parameter = enumeration.nextElement();
            parameterMap.put(parameter,request.getParameter(parameter));
        }
        String str = JSON.toJSONString(parameterMap);
        if(obj.length > 0) {
            System.out.println("请求的参数信息为："+str);
        }

    }


    /**
     * @AfterReturning:
     *  在某连接点之后执行的通知，
     *  通常在一个匹配的方法返回的时候执行（可以在后置通知中绑定返回值）
     */
    @AfterReturning(value = "pointcut()")
    public void afterReturning(){
        System.out.println("在返回值之前执行......");
    }


    /**
     *  @AfterThrowing:
     *   在方法抛出异常退出时执行的通知。
     *   throwing:限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知，否则不执行，
     *  *           对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。
     *  @param joinPoint
     *  @param exception
     */
    @AfterThrowing(value = "pointcut()",throwing = "exception")
    public void afterThrowing(JoinPoint joinPoint, Throwable exception){
        //目标方法名：
        System.out.println("目标方法名称:"+ joinPoint.getSignature().getName());
        if(exception instanceof NullPointerException){
            System.out.println("发生了空指针异常!!!!!");
        }
    }

    /**
     * @After
     * 后置最终通知（目标方法只要执行完了就会执行后置通知方法）
     */
    @After(value = "pointcut()")
    public void after(){
        System.out.println("后置最终通知执行了!!!!");
    }


    /**
     * 环绕通知：
     *  环绕通知非常强大，可以决定目标方法是否执行，什么时候执行，
     *  执行时是否需要替换方法参数，执行完毕是否需要替换返回值。
     *  *   环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
     * @param proceedingJoinPoint
     * 环绕通知开启后 其他aop注解失效
     */
//    @Around(value = "pointcut()")
    public void around(ProceedingJoinPoint proceedingJoinPoint){
        System.out.println("环绕通知的目标方法名："+proceedingJoinPoint.getSignature().getName());
    }

    /**
     *定义切面的时候，切面中需要使用到目标对象的某个参数,可以使用args来绑定。
     * 如果在一个args表达式中应该使用类型名字的地方使用一个参数名字，
     * 那么当通知执行的时候对象的参数值将会被传递进来
     */
    @Before("execution(* findById*(..)) &&" + "args(id,..)")
    public void getAopParam(Long id){
        System.err.println ("切面before执行了id==" + id);
    }

    /**
     * 切入点表达式关键词用例：
     * 1）execution：用于匹配子表达式。
     * //匹配com.cjm.model包及其子包中所有类中的所有方法，返回类型任意，方法参数任意
     * @Pointcut(“execution(* com.cjm.model...(..))”)
     * public void before(){}
     *
     * 2）within：用于匹配连接点所在的Java类或者包。
     * //匹配Person类中的所有方法
     * @Pointcut(“within(com.cjm.model.Person)”)
     * public void before(){}
     * //匹配com.cjm包及其子包中所有类中的所有方法
     * @Pointcut(“within(com.cjm..*)”)
     * public void before(){}
     *
     * 3） this：用于向通知方法中传入代理对象的引用。
     * @Before(“before() && this(proxy)”)
     * public void beforeAdvide(JoinPoint point, Object proxy){
     * //处理逻辑
     * }
     *
     * 4）target：用于向通知方法中传入目标对象的引用。
     * @Before(“before() && target(target)
     * public void beforeAdvide(JoinPoint point, Object proxy){
     * //处理逻辑
     * }
     *
     * 5）args：用于将参数传入到通知方法中。
     * @Before(“before() && args(age,username)”)
     * public void beforeAdvide(JoinPoint point, int age, String username){
     * //处理逻辑
     * }
     *
     * 6）@within ：用于匹配在类一级使用了参数确定的注解的类，其所有方法都将被匹配。
     * @Pointcut(“@within(com.cjm.annotation.AdviceAnnotation)”)
     * － 所有被@AdviceAnnotation标注的类都将匹配
     * public void before(){}
     *
     * 7）@target ：和@within的功能类似，但必须要指定注解接口的保留策略为RUNTIME。
     * @Pointcut(“@target(com.cjm.annotation.AdviceAnnotation)”)
     * public void before(){}
     *
     * 8）@args ：传入连接点的对象对应的Java类必须被@args指定的Annotation注解标注。
     * @Before(“@args(com.cjm.annotation.AdviceAnnotation)”)
     * public void beforeAdvide(JoinPoint point){
     * //处理逻辑
     * }
     *
     * 9）@annotation ：匹配连接点被它参数指定的Annotation注解的方法。也就是说，所有被指定注解标注的方法都将匹配。
     * @Pointcut(“@annotation(com.cjm.annotation.AdviceAnnotation)”)
     * public void before(){}
     *
     * 10）bean：通过受管Bean的名字来限定连接点所在的Bean。该关键词是Spring2.5新增的。
     * @Pointcut(“bean(person)”)
     * public void before(){}
     */

}
